use proc_macro2::TokenStream;
use quote::{format_ident, quote};


#[derive(Debug)]
pub enum OutputMode {
    PP,
    OD,
    AFPP,
    AFOD,
}

#[derive(Debug)]
pub enum InputMode {
    FLOAT,
    UP,
    DOWN,
}

#[derive(Debug)]
pub enum Mode {
    Out(OutputMode),
    In(InputMode),
    Analog,
}

impl Mode {

    fn mode(&self) -> TokenStream {
        match self {
            Mode::In(_) | Mode::Analog => quote! { input() },
            Mode::Out(_) => quote! { output50() },
        }
    }

    fn cnf(&self) -> TokenStream {
        match self {
            Mode::Out(t) => {
                match t {
                    OutputMode::PP => quote! { push_pull() },
                    OutputMode::OD => quote! { open_drain() },
                    OutputMode::AFOD => quote! { alt_open_drain() },
                    OutputMode::AFPP => quote! { alt_push_pull() },
                }
            },
            Mode::In(t) => {
                match t {
                    InputMode::UP | InputMode::DOWN => quote! { alt_push_pull() },
                    InputMode::FLOAT => quote! { open_drain() },
                }
            },
            Mode::Analog => {
                quote! { push_pull() }
            }
        }
    }
}

#[derive(Debug)]
pub struct Pin<'a> {
    pub name: &'a str,
    pub pin: u8,
}

#[derive(Debug)]
pub struct PinConfig<'a> {
    name: String,
    pub pin: Pin<'a>,
    mode: Mode,
}

fn parse_mode(attr: &[&str]) -> Mode {

    match attr[0] {
        "OUT" => {
            match attr[1] {
                "PP" => Mode::Out(OutputMode::PP),
                "OD" => Mode::Out(OutputMode::OD),
                _ => panic!("invalid:{:?}", attr),
            }
        },
        "AF" => {
            match attr[1] {
                "PP" => Mode::Out(OutputMode::AFPP),
                "OD" => Mode::Out(OutputMode::AFOD),
                _ => panic!("invalid:{:?}", attr),
            }
        },
        "IN" => {
            match attr[1] {
                "FLOAT" => Mode::In(InputMode::FLOAT),
                "UP" => Mode::In(InputMode::UP),
                "DOWN" => Mode::In(InputMode::DOWN),
                _ => panic!("invalid:{:?}", attr),
            }
        },
        "ANALOG" => {
            Mode::Analog
        }
        _ => panic!("invalid mode:{:?}", attr)
    }
}

impl<'a> PinConfig<'a> {

    fn create_cr(&self) -> TokenStream {
        let mode = format_ident!("mode{}", self.pin.pin);
        let cnf = format_ident!("cnf{}", self.pin.pin);
        let mode_val = self.mode.mode();
        let cnf_val = self.mode.cnf();
        quote! {
            .#mode().#mode_val.#cnf().#cnf_val
        }
    }

    fn create_bsrr(&self) -> Option<TokenStream> {
        match self.mode {
            Mode::In(InputMode::UP) | Mode::Out(OutputMode::OD) => {
                let bsrr = format_ident!("bs{}", self.pin.pin);
                let expand = quote! {
                    .#bsrr().set_bit()
                };
                return Some(expand);
            }
            _ => {}
        };
        None
    }

    fn create_brr(&self) -> Option<TokenStream> {
        match self.mode {
            Mode::In(InputMode::DOWN) | Mode::Out(OutputMode::PP) => {
                let brr = format_ident!("br{}", self.pin.pin);
                let expand = quote! {
                    .#brr().set_bit()
                };
                return Some(expand);
            }
            _ => {}
        }
        None
    }

    fn create_set_pin(&self) -> TokenStream {
        let port = format_ident!("P{}", self.pin.name);
        let bs = format_ident!("bs{}", self.pin.pin);
        let name = format_ident!("{}_set_high", self.name);
        quote! {
            #[inline(always)]
            pub fn #name(&self) {
                #port.bsrr.write(|w| {
                    w.#bs().set_bit()
                });
            }
        }
    }

    fn create_clear_pin(&self) -> TokenStream {
        let port = format_ident!("P{}", self.pin.name);
        let br = format_ident!("br{}", self.pin.pin);
        let name = format_ident!("{}_set_low", self.name);
        quote! {
            #[inline(always)]
            pub fn #name(&self) {
                #port.brr.write(|w| {
                    w.#br().set_bit()
                });
            }
        }
    }

    fn create_read_pin(&self) -> TokenStream {
        let port = format_ident!("P{}", self.pin.name);
        let idr = format_ident!("idr{}", self.pin.pin);
        let name = format_ident!("{}_read", self.name);
        quote! {
            #[inline(always)]
            pub fn #name(&self) -> bool {
                #port.idr.read().#idr().is_high()
            }
        }
    }

    fn create_toggle(&self) -> TokenStream {
        let port = format_ident!("P{}", self.pin.name);
        let odr = format_ident!("odr{}", self.pin.pin);
        let name = format_ident!("{}_toggle", self.name);
        quote! {
            #[inline(always)]
            pub fn #name(&self) {
                #port.odr.modify(|r, w| {
                    w.#odr().bit( !r.#odr().bit() )
                });
            }
        }
    }
}

fn parse_pin<'a>(attr: &'a str) -> Pin<'a> {
    let name = &attr[1..2];
    let pin: u8 = (&attr[2..]).parse().unwrap();
    Pin { name, pin }
}

pub fn parse_pin_config<'a>(attr: &'a str) -> PinConfig<'a> {
    let attr = attr.trim();
    let configs: Vec<&str> = attr.split("_").collect();
    
    let name = configs[0].to_lowercase();
    let pin = parse_pin(configs[1]);
    let mode = parse_mode(&configs[2..]);
    PinConfig { name, pin, mode }
}

pub fn create_crl(name: &str, configs: &[PinConfig]) -> TokenStream {

    let configs: Vec<&PinConfig> = configs.iter().filter(|c| c.pin.pin < 8).collect();
    let mut vec = Vec::new();
    for c in configs {
        vec.push( c.create_cr() );
    }
    let port = format_ident!("P{}", name.to_uppercase());
    let mut expand = quote! {};
    if !vec.is_empty() {
        let temp = quote! {
            #port.crl.modify(|_, w| {
                w #(#vec)*
            });
        };
        expand.extend(temp);
    }
    expand
}

pub fn create_crh(name: &str, configs: &[PinConfig]) -> TokenStream {

    let configs: Vec<&PinConfig> = configs.iter().filter(|c| c.pin.pin >= 8).collect();
    let mut vec = Vec::new();
    for c in configs {
        vec.push( c.create_cr() );
    }
    let port = format_ident!("P{}", name.to_uppercase());
    let mut expand = quote! {};

    if !vec.is_empty() {
        let temp = quote! {
            #port.crh.modify(|_, w| {
                w #(#vec)*
            });
        };
        expand.extend(temp);
    }
    expand
}

pub fn create_odr(name: &str, configs: &[PinConfig]) -> TokenStream {
    let mut bsrr = Vec::new();
    let mut brr = Vec::new();
    for conf in configs {
        if let Some(token) = conf.create_bsrr() {
            bsrr.push(token);
        }
        if let Some(token) = conf.create_brr() {
            brr.push(token);
        }
    }
    let mut expand = quote! {};

    let port = format_ident!("P{}", name.to_uppercase());

    if !bsrr.is_empty() {
        let temp = quote! {
            #port.bsrr.write(|w| {
                w #(#bsrr)*
            });
        };
        expand.extend(temp);
    }

    if !brr.is_empty() {
        let temp = quote! {
            #port.brr.write(|w| {
                w #(#brr)*
            });
        };
        expand.extend(temp);
    }
    expand
}

pub fn create_other(configs: &[PinConfig]) -> TokenStream {
    let mut expand = quote! {};
    for config in configs {
        expand.extend( config.create_set_pin() );
        expand.extend( config.create_clear_pin() );
        expand.extend( config.create_read_pin() );
        expand.extend(config.create_toggle());
    }
    expand
}

